package com.gejingyu.bean;

import com.gejingyu.annotation.Bean;
import com.gejingyu.annotation.Di;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class AnnotationApplicationContext implements ApplicationContext {

    //创建map集合，存放bean对象
    private Map<Class, Object> beanFactory = new HashMap<>();

    //要扫描的包的绝对路径中，包名前面的固定路径
    private static String rootPath;

    //返回创建好的对象
    @Override
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);
    }

    //创建有参构造器，传递包路径，设置包扫描规则
    //对当前包及其子包中，使用@Bean注解的类，进行反射实例化
    public AnnotationApplicationContext(String basePackage) {

        //将包路径中的.替换成\，注意转义字符
        String packagePath = basePackage.replaceAll("\\.", "\\\\");

        //通过当前线程获取类加载器，进而获取包的绝对路径
        try {
            Enumeration<URL> urls
                    = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            //注意这里获取到的是编译后运行的类文件，也就是项目/target路径下的文件
            while (urls.hasMoreElements()) {
                //包的绝对路径
                URL url = urls.nextElement();
                //对url进行转码
                String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                //获取包名前的固定路径
                rootPath = filePath.substring(0, filePath.length() - packagePath.length());
                //对包中的类进行扫描
                loadBean(new File(filePath));
                //对beanFactory中的bean对象进行依赖注入
                dependencyInjection();
            }
        } catch (IOException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException |
                 InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * <p>根据传入的包文件，对该包及其子包中的类进行扫描</p>
     * <p>如果类中使用@Bean注解，则创建类对象并放入beanFactory</p>
     * @param file 要扫描的包路径文件夹
     * @throws ClassNotFoundException 由Class.forName()方法，在类路径错误、无法获取Class对象时抛出
     * @throws NoSuchMethodException 由clazz.getConstructor()方法，在要创建的bean对象不存在无参构造时抛出
     * @throws InvocationTargetException 由clazz.getConstructor().newInstance()方法所调用的类的构造方法本身抛出的异常
     * @throws InstantiationException 由clazz.getConstructor().newInstance()方法，在bean对象是抽象类的时候抛出
     * @throws IllegalAccessException 由clazz.getConstructor().newInstance()方法，在实际参数与形参的个数或类型不一致时抛出
     */
    private void loadBean(@NotNull File file) throws ClassNotFoundException, NoSuchMethodException,
            InvocationTargetException, InstantiationException, IllegalAccessException {
        //当前File是文件夹
        if (file.isDirectory()) {
            //获取当前文件夹中的所有文件
            File[] fileList = file.listFiles();
            //当前文件夹为空时，直接返回
            if (fileList == null || fileList.length == 0) {
                return;
            }
            //循环当前文件夹中的文件
            for (File child : fileList) {
                if (child.isDirectory()) {
                    //如果当前文件是文件夹，则递归调用
                    loadBean(child);
                } else if (child.getAbsolutePath().endsWith(".class")) {
                    //当前文件是.class类型文件
                    //获取包路径+类名称：从类的绝对路径中截取掉固定路径，截取掉最后的.class
                    String classPath = child.getAbsolutePath().replaceAll("\\\\",".")
                            .substring(rootPath.length() - 1, child.getAbsolutePath().length() - 6);
                    //获取类对象
                    Class<?> clazz = Class.forName(classPath);
                    //判断当前类是不是接口（只需要扫描接口的实现类）
                    if (!clazz.isInterface()) {
                        //获取类中的@Bean注解
                        Bean annotation = clazz.getAnnotation(Bean.class);
                        if (annotation != null) {
                            //类中存在@bean注解，进行实例化
                            Object instance = clazz.getConstructor().newInstance();
                            //当前类是否实现了接口，如果实现接口，则用接口作为beanFactory的key，否则用自身作为key
                            if (clazz.getInterfaces().length > 0) {
                                //此处以第一个接口为例，可以在加上对接口的循环放入，以及一个接口多个实现类的判断
                                beanFactory.put(clazz.getInterfaces()[0], instance);
                            } else {
                                beanFactory.put(clazz, instance);
                            }
                        }
                    }
                }
            }
        }

    }

    /**
     * <P>对beanFactory中的bean对象进行依赖注入</P>
     * <p>如果bean对象的属性使用了@Di注解，则从beanFactory中获取对应的对象进行赋值</p>
     * @throws IllegalAccessException 由field.set()方法，在field无法访问修改（如属性有private或final修饰）时抛出
     */
    private void dependencyInjection() throws IllegalAccessException {
        //对beanFactory中的bean对象进行遍历
        Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
        for (Map.Entry<Class, Object> entry : entries) {
            //获取bean的类对象
            Object object = entry.getValue();
            Class<?> clazz = object.getClass();
            //获取类对象中的所有属性，注意使用getDeclaredFields()方法，获取私有属性
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                Di annotation = field.getAnnotation(Di.class);
                //如果属性使用了@Di注解，则进行属性注入
                if (annotation != null){
                    //考虑到属性可能是private，先设置成可访问的
                    field.setAccessible(true);
                    //根据属性的类型获取对应的bean对象进行注入
                    field.set(object, beanFactory.get(field.getType()));
                }
            }
        }
    }
}
